package com.uow.algdemo.animation;

import java.util.List;

import com.uow.algdemo.material.Line;
import com.uow.algdemo.material.Point;

import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Color;
import android.os.Handler;
import android.os.Message;
import android.view.MotionEvent;
import android.view.View;
import android.widget.EditText;
import android.widget.RelativeLayout;
import android.widget.Toast;

/**
 * This class will receive data of graph and animation steps, then generate animation on animation layout.
 */
public class AnimationGraph extends RelativeLayout implements AnimationInterface
{
	/**
     * This is handler for whole progress of animation.
     */
	private Handler handler;
	/**
     * This is handler for animation of highlight lines.
     */
	private Handler handler_c;
	/**
     * This is current status of disks moving or not.
     */
	private boolean running = false;
	/**
     * This is current status of animation completed or not.
     */
	private boolean finished = false;
	/**
     * This is current status of pause or not.
     */
	private boolean pause = false;
    /**
     * This is number of edges.
     */
	private int eCount = 0;
    /**
     * This is number of points.
     */
	private int pCount = 0;
	/**
     * This array contains view of edges.
     */
	private Line[] edges;
	/**
     * This array contains view of points.
     */
	private Point[] points;
	/**
     * This array contains coods of points.
     */
	private int[][] coods;
	/**
     * This array contains relations of points and edges.
     */
	private int[][] matrix;
	/**
     * This is status of drawing or not.
     */
	private boolean drawing = false;
	/**
     * This is status of moving point or not.
     */
	private boolean moving = false;
	/**
     * This is points' name array.
     */
	private String pName = "ABCDEFGHIJ";
	/**
     * This is index of start point when drawing.
     */
	private int sIndex;
	/**
     * This is index of end point when drawing.
     */
	private int eIndex;
	/**
     * This is index of point when moving.
     */
	private int mIndex;
    /**
     * This array contains edges of graph and the length between two points.
     */
	private int[][] edgesArray;
	/**
     * This array contains steps of shortest path or minimum spanning tree solution.
     */
	private List<int[][]> steps;
    /**
     * This is the current step of animation position.
     */
	private int position = 0;
    /**
     * This is the current speed of animation.
     */
	private int speed = 10;
    /**
     * This is the current drawing method (0 - none, 1 - draw point, 2 - draw line).
     */
    private int tools = 0;
    /**
     * This is the size of a point or number.
     */
	private int size = 0;
    /**
     * This is the height of current layout.
     */
    private int height = 0;
    /**
     * This is the width of current layout.
     */
    private int width = 0;
    /**
     * This is the listener of touch events on screen.
     */
    private View.OnTouchListener otl = new View.OnTouchListener()
	{
        public boolean onTouch(View v, MotionEvent event)
        {
        	
            if (event.getAction() == MotionEvent.ACTION_DOWN)
            {
            	if(tools == 1 && pCount < 10)
            	{
            		mIndex = checkPoint((int) (height - event.getY()),(int) (event.getX()));
            		if(mIndex == -1)
            		{
		            	points[pCount] = new Point(AnimationGraph.this.getContext(), pName.substring(pCount, pCount + 1), size);
		            	points[pCount].invalidate();
		        		RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(size, size);
		        		params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
		        		params.bottomMargin = (int) (height - event.getY() - size / 2);
		        		params.leftMargin = (int)event.getX() - size / 2;
		        		coods[pCount][0] = (int) (height - event.getY());
		        		coods[pCount][1] = (int) (event.getX());
		        		AnimationGraph.this.addView(points[pCount],params);
		            	pCount++;
            		}
            		else
            		{
            			moving = true;
            		}
            	}
            	if(tools == 2)
            	{
            		sIndex = checkPoint((int) (height - event.getY()),(int) (event.getX()));
            		if(sIndex >= 0)
            		{
            			edges[eCount] = new Line(AnimationGraph.this.getContext(),coods[sIndex][1], height - coods[sIndex][0]);
            			drawing = true;
            		}
            	}
            }
            if (event.getAction() == MotionEvent.ACTION_MOVE)
            {
            	if(tools == 1 && moving)
            	{
	            	AnimationGraph.this.removeView(points[mIndex]);
	        		RelativeLayout.LayoutParams mparams = new RelativeLayout.LayoutParams(size, size);
	        		mparams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
	        		mparams.bottomMargin = (int) (height - event.getY() - size / 2);
	        		mparams.leftMargin = (int)event.getX() - size / 2;
	        		coods[mIndex][0] = (int) (height - event.getY());
	        		coods[mIndex][1] = (int) (event.getX());
	        		points[mIndex].invalidate();
	        		AnimationGraph.this.addView(points[mIndex],0,mparams);

	        		for(int i = 0; i < 10; i++)
	        		{
                		if(edgesArray[i][mIndex] != 1000)
                		{
                			int tmpIndex = matrix[i][mIndex];
        	            	AnimationGraph.this.removeView(edges[tmpIndex]);
                			edges[tmpIndex] = new Line(AnimationGraph.this.getContext(),coods[i][1], height - coods[i][0]);
			        		edges[tmpIndex].setEnd(coods[mIndex][1],height - coods[mIndex][0]);
			        		edges[tmpIndex].setLength(edgesArray[i][mIndex]);
			        		RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(width, height);
			        		edges[tmpIndex].invalidate();
			        		AnimationGraph.this.addView(edges[tmpIndex],0,params);
                		}
	        		}
	        	}
            	if(tools == 2 && drawing)
            	{
	            	AnimationGraph.this.removeView(edges[eCount]);
	            	edges[eCount].setEnd((int)event.getX(),(int)event.getY());
	        		RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(width, height);
	        		edges[eCount].invalidate();
	        		AnimationGraph.this.addView(edges[eCount],0,params);
            	}
            }
            if (event.getAction() == MotionEvent.ACTION_UP)
            {
            	if(tools == 1 && moving)
            	{
            		moving = false;
            	}
            	if(tools == 2)
            	{
            		eIndex = checkPoint((int) (height - event.getY()),(int) (event.getX()));
            		if(eIndex >= 0 && sIndex >= 0)
            		{
	            		if(edgesArray[sIndex][eIndex] == 1000 && edgesArray[eIndex][sIndex] == 1000)
	            		{
		            		if(eIndex >= 0 && eIndex != sIndex && drawing)
		            		{
				            	AnimationGraph.this.removeView(edges[eCount]);
				            	edges[eCount].setEnd(coods[eIndex][1], height - coods[eIndex][0]);
				        		RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(width, height);
				        		edges[eCount].invalidate();
				        		AnimationGraph.this.addView(edges[eCount],0,params);

				        		final EditText inputValue = new EditText(AnimationGraph.this.getContext());
		                        inputValue.setInputType(0x00000002);
		                        AlertDialog.Builder builder = new AlertDialog.Builder(AnimationGraph.this.getContext());
		                        builder.setTitle("Input length of edge (1-99)");
		                        builder.setIcon(android.R.drawable.ic_dialog_info);
		                        builder.setView(inputValue);
		                        builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener()
		                        {
		                            public void onClick(DialogInterface dialog, int which)
		                            {
		                            	AnimationGraph.this.removeView(edges[eCount]);
				            			drawing = false;
		                            }
		                        });
		                        builder.setPositiveButton("Ok", new DialogInterface.OnClickListener()
		                        {
		                            public void onClick(DialogInterface dialog, int which)
		                            {
		                            	if(inputValue.getText().toString().length() > 0)
		                            	{
			                            	int input = Integer.parseInt(inputValue.getText().toString());
			                            	if(input > 0 && input < 100)
			                            	{
			                            		edges[eCount].setLength(input);
			                            		edgesArray[sIndex][eIndex] = input;
			                            		edgesArray[eIndex][sIndex] = input;
			                            		matrix[sIndex][eIndex] = eCount;
			                            		matrix[eIndex][sIndex] = eCount;
						            			eCount++;
						            			drawing = false;
						            			eIndex = -1;
						            			sIndex = -1;
			                            	}
			                            	else
			                            	{
			                            		AnimationGraph.this.removeView(edges[eCount]);
						            			drawing = false;
			                            		Toast mes = Toast.makeText(AnimationGraph.this.getContext(), "Input is not vaild", Toast.LENGTH_SHORT);
			                            		mes.show();
						            			eIndex = -1;
						            			sIndex = -1;
			                            	}
		                            	}
		                            	else
		                            	{
		                            		AnimationGraph.this.removeView(edges[eCount]);
					            			drawing = false;
		                            		Toast mes = Toast.makeText(AnimationGraph.this.getContext(), "Input is not vaild", Toast.LENGTH_SHORT);
		                            		mes.show();
					            			eIndex = -1;
					            			sIndex = -1;
		                            	}
		                            }
		                        });
		                        builder.show();
		            		}
		            		else
		            		{
		            			AnimationGraph.this.removeView(edges[eCount]);
		            			drawing = false;
		            			eIndex = -1;
		            			sIndex = -1;
		            		}
	            		}
	            		else
	            		{
                    		Toast mes = Toast.makeText(AnimationGraph.this.getContext(), "Edge already exists", Toast.LENGTH_SHORT);
                    		mes.show();
	            			AnimationGraph.this.removeView(edges[eCount]);
	            			drawing = false;
	            			eIndex = -1;
	            			sIndex = -1;
	            		}
            		}
            		else
            		{
                		AnimationGraph.this.removeView(edges[eCount]);
            			drawing = false;
            			eIndex = -1;
            			sIndex = -1;
            		}
            	}
            }
            return true;
        }
    };
	/**
     * @param context The Context the view is running in, through which it can access the current theme, resources, etc.
     * @param width Width of layout in pixel.
     * @param height Height of layout in pixel.
     * @param psize Size of point in pixel.
     */
	public AnimationGraph(final Context context, final int width, final int height, int psize)
	{
        super(context);
        this.height = height;
        this.width = width;

        size = psize;

        points = new Point[10];
        edges = new Line[30];
        coods = new int[10][2];
        matrix = new int[10][10];
        edgesArray = new int[10][10];
        
        for(int i = 0; i < 10; i++)
        {
        	for(int j = 0; j < 10; j++)
        	{
        		edgesArray[i][j] = 1000;
        	}
        }

        this.setBackgroundColor(Color.WHITE);
		this.setOnTouchListener(otl);
	}
	/**
     * @param context The Context the view is running in, through which it can access the current theme, resources, etc.
     * @param newTotal Number of disks.
     * @param newSteps Steps of the queens problem solution animation.
     * @param width Width of layout in pixel.
     * @param height Height of layout in pixel.
     * @param psize Size of point in pixel.
     */
	public AnimationGraph(final Context context, int[][] eArray, int[][] cArray, final int width, final int height, int psize)
	{
        super(context);
        this.height = height;
        this.width = width;

        size = psize;

        points = new Point[10];
        edges = new Line[30];
        matrix = new int[10][10];
        coods = cArray;
        edgesArray = new int[10][10];
        for(int i = 0; i < 10; i++)
        {
        	for(int j = 0; j < 10; j++)
        	{
        		edgesArray[i][j] = eArray[i][j];
        	}
        }
        
        for(int i = 0; i < 10; i++)
        {
        	if(coods[i][0] != 0 && coods[i][1] != 0)
        	{
        		points[pCount] = new Point(context, pName.substring(pCount, pCount + 1), size);
        		RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(size, size);
        		params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
        		params.bottomMargin = (int) (coods[i][0]) - size / 2;
        		params.leftMargin = (int)coods[i][1] - size / 2;
        		AnimationGraph.this.addView(points[pCount],params);
        		pCount++;
        	}
        }

        for(int i = 0; i < 10; i++)
        {
            for(int j = 0; j < 10; j++)
            {
	        	if(eArray[i][j] != 1000)
	        	{
        			edges[eCount] = new Line(context,coods[i][1], height - coods[i][0]);
	            	edges[eCount].setEnd(coods[j][1], height - coods[j][0]);
	        		RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(width, height);
	        		edges[eCount].invalidate();
	        		AnimationGraph.this.addView(edges[eCount],0,params);
            		edges[eCount].setLength(edgesArray[i][j]);
            		matrix[i][j] = eCount;
            		matrix[j][i] = eCount;
            		eArray[j][i] = 1000;
            		eCount++;
	        	}
            }
        }

        this.setBackgroundColor(Color.WHITE);
		this.setOnTouchListener(otl);
	}

	/**
     * This method find the index of point which current coods belong to.
     * @param x Cood of x.
     * @param y Cood of y.
	 * @return The index of point
     */
	private int checkPoint(int x, int y)
	{
		for(int i = 0; i < pCount; i++)
		{
			if(x < (coods[i][0] + size / 2) && x > (coods[i][0] - size / 2) && y > (coods[i][1] - size / 2) && y < (coods[i][1] + size / 2))
			{
				return i;
			}
		}
		return -1;
	}
	/**
	 * @param steps The steps to set
	 */
	public void setSteps(List<int[][]> steps)
	{
		this.steps = steps;
	}
	/**
	 * @return The speed
	 */
	public int getSpeed()
	{
		return speed;
	}
	/**
	 * @return The edges
	 */
	public int[][] getEdges()
	{
		return edgesArray;
	}
    /**
     * This method set speed of animation.
     * @param newSpeed Speed value to set.
     */	
	public void setSpeed(int newSpeed)
	{
		speed = newSpeed;
	}
	/**
	 * @return The status
	 */
	public boolean getStatus()
	{
		return finished;
	}
    /**
     * This method send feedback from this class for different command.
     * @param mes Message to current class.
     * @return An array for feedback information.
     */
	public int[][] sendMessage(int mes)
	{
		int tmp[][] = new int[1][2];
		switch(mes)
		{
			case 0:
			{
				tools = mes;
				break;
			}
			case 1:
			{
				tools = mes;
				break;
			}
			case 2:
			{
				tools = mes;
				break;
			}
			case 3:
			{
				tmp = edgesArray;
				break;
			}
			case 4:
			{
				tmp = coods;
				break;
			}
			case 5:
			{
				tmp[0][0] = eCount;
				tmp[0][1] = pCount;
				break;
			}
		}
		return tmp;
	}
	/**
     * This method generate animation and start animation.
     */	
	@SuppressLint("HandlerLeak")
	public void runAnimation()
	{
		tools = 0;
		handler = new Handler()
		{
			public void handleMessage(Message msg)
			{
				switch (msg.what)
				{
					case 1:
					{
						if(!pause)
						{
							if(position < steps.size())
							{
								int[][] a = steps.get(position);
								switch(a[0][0])
								{
									case 0:
									{
										highlight(a[1][0],a[2][0]);
										break;
									}
									case 1:
									{
										select(a[1][0],a[2][0]);
										break;
									}
									case 2:
									{
										unselect(a[1][0],a[2][0]);
										break;
									}
									case 3:
									{
										delete(a[1][0],a[2][0]);
										break;
									}
								}
								position++;
							}
							else
							{
								finished = true;
							}
						}
						break;
					}
				}
				super.handleMessage(msg);
			}
		};
		
		handler.sendEmptyMessage(1);
	}
    /**
     * This method pause animation.
     */	
	public void pauseAnimation()
	{
		pause = true;
	}
    /**
     * This method resume animation from pause position.
     */	
	public void resumeAnimation()
	{
		pause = false;
		if(!running)
		{
			handler.sendEmptyMessage(1);
		}
	}
    /**
     * This method stop animation and clear layout.
     */	
	public void stopAnimation()
	{
		finished = true;
	}
    /**
     * This method show next step of animation when animation is pause.
     */	
	public void nextStep()
	{
		if(pause == true && running == false)
		{
			if(position < steps.size())
			{
				int[][] a = steps.get(position);
				switch(a[0][0])
				{
					case 0:
					{
						highlight(a[1][0],a[2][0]);
						break;
					}
					case 1:
					{
						select(a[1][0],a[2][0]);
						break;
					}
					case 2:
					{
						unselect(a[1][0],a[2][0]);
						break;
					}
					case 3:
					{
						delete(a[1][0],a[2][0]);
						break;
					}
				}
				position++;
			}
		}
	}
    /**
     * This method show last step of animation when animation is pause.
     */	
	public void lastStep()
	{
		if(pause == true && running == false)
		{
			if(position > 0)
			{
				position--;
				int[][] a = steps.get(position);
				switch(a[0][0])
				{
					case 0:
					{
						highlight(a[1][0],a[2][0]);
						break;
					}
					case 1:
					{
						select(a[1][0],a[2][0]);
						break;
					}
					case 2:
					{
						unselect(a[1][0],a[2][0]);
						break;
					}
					case 3:
					{
						delete(a[1][0],a[2][0]);
						break;
					}
				}
			}
		}
	}
    /**
     * This method generate animation for highlight one edge.
     * @param indexPointA Index of first point.
     * @param indexPointB Index of second point.
     */
	private void highlight(final int indexPointA, final int indexPointB)
	{
		running = true;
		edges[matrix[indexPointA][indexPointB]].colorBlue();
		handler_c = new Handler()
		{
			public void handleMessage(Message msg)
			{
				switch (msg.what)
				{
					case 1:
					{
						edges[matrix[indexPointA][indexPointB]].colorBlack();
						running = false;
						handler.sendEmptyMessageDelayed(1, speed * 20);
						break;
					}
				}
				super.handleMessage(msg);
			}
		};
		
		handler_c.sendEmptyMessageDelayed(1, speed * 80);
	}

    /**
     * This generate animation steps for select one edge on graph.
     * @param indexPointA Index of first point.
     * @param indexPointB Index of second point.
     */
	private void select(int indexPointA, int indexPointB)
	{
		edges[matrix[indexPointA][indexPointB]].colorRed();
		handler.sendEmptyMessageDelayed(1, speed * 100);
	}
    /**
     * This generate animation steps for unselect one edge on graph.
     * @param indexPointA Index of first point.
     * @param indexPointB Index of second point.
     */
	private void unselect(int indexPointA, int indexPointB)
	{
		edges[matrix[indexPointA][indexPointB]].colorBlack();
		handler.sendEmptyMessageDelayed(1, speed * 100);
	}
    /**
     * This method generate animation for delete one edge from graph.
     * @param indexPointA Index of first point.
     * @param indexPointB Index of second point.
     */
	private void delete(int indexPointA, int indexPointB)
	{
		edges[matrix[indexPointA][indexPointB]].setVisibility(4);
		handler.sendEmptyMessageDelayed(1, speed * 100);
	}
}
